package com.jplus.core.core;

import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;

import com.jplus.core.anno.boot.ComponentScan;
import com.jplus.core.anno.boot.Configuration;
import com.jplus.core.anno.boot.Import;
import com.jplus.core.anno.boot.PropertySource;
import com.jplus.core.anno.com.Scope.EScope;
import com.jplus.core.core.abstracts.BeanDefinitions;
import com.jplus.core.core.abstracts.BeanFactory;
import com.jplus.core.core.abstracts.Environment;
import com.jplus.core.core.abstracts.Environment.FRAME;
import com.jplus.core.core.abstracts.FactoryBean;
import com.jplus.core.core.beans.BeanDefinition;
import com.jplus.core.core.beans.BeanKey;
import com.jplus.core.core.events.ApplicationEvent;
import com.jplus.core.core.events.ApplicationEventPublisher;
import com.jplus.core.core.events.ApplicationListener;
import com.jplus.core.core.proxys.ProxyManager;
import com.jplus.core.fault.BeansException;
import com.jplus.core.fault.InitializationError;
import com.jplus.core.plugin.ImportSelector;
import com.jplus.core.utill.Assert;
import com.jplus.core.utill.JUtil;
import com.jplus.core.utill.StreamUtil;
import com.jplus.core.utill.clazz.ClassUtil;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 职责：加载配置项，初始化Bean容器，管理IOC,AOP
 * 
 * @author yuanqy
 */
public abstract class ApplicationContext implements Configure, ApplicationEventPublisher {
	private static final Logger log = LoggerFactory.getLogger(ApplicationContext.class);

	static {
		if (log.isDebugEnabled()) {
			log.debug("==System====================================");
			Properties prop = System.getProperties();
			for (Entry<Object, Object> en : prop.entrySet())
				log.debug(">>{}:{}", en.getKey(), en.getValue());
		}
	}

	private boolean state;

	private List<Class<?>> importClassList = new ArrayList<>();
	protected Environment environment;
	protected BeanDefinitions beanDefinition;
	protected BeanFactory beanFactory;
	protected FactoryBean factoryBean;

	protected ProxyManager proxyManager;

	public abstract ProxyManager getProxyManager();

	protected ApplicationContext() {
		postProcessBanner();
		environment = getEnvironment();
		beanDefinition = getBeanDefinitions();
		beanFactory = getBeanFactory();
		factoryBean = getFactoryBean();
	}

	protected void refresh() {
		try {
			synchronized (this) {
				state = true;
				environment.loadConfigFile(environment.getProp(FRAME.FILE_PROP));
				beanDefinition.buildBeanDefinition(environment.getProp(FRAME.COMPONENT_SCAN));
				postProcessImportClass();
				postProcessBuildBeans();
				postConstruct();
				Runtime.getRuntime().addShutdownHook(new ShutdownHook(this));
			}
		} catch (Exception e) {
			log.error("Exception:", e);
			throw new InitializationError(e);
		}
	}

	public void postConstruct() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		List<BeanDefinition> list = beanDefinition.listSortBeanDefinitions();
		log.info("[BEAN]:{}", list.size());
		for (BeanDefinition bd : list) {
			log.info("\t>> {}", bd.getBeanKey());
			excuteMethod(bd, bd.getPostConstruct());
			excuteMethod(bd, bd.getInitMethod());
		}
	}

	private void excuteMethod(BeanDefinition bd, Method m)
			throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		if (m != null)
			m.invoke(beanFactory.getBean(bd.getBeanKey()), new Object[] {});
	}

	public void close() {
		preDestory();
	}

	protected void preDestory() {
		state = false;
		log.warn("##### Frame is destoryed.\n");
		try {
			List<BeanDefinition> list = beanDefinition.listSortBeanDefinitions();
			for (BeanDefinition bd : list) {
				excuteMethod(bd, bd.getPreDestory());
				excuteMethod(bd, bd.getDestoryMethod());
			}
		} catch (Exception e) {
			log.error("PreDestory Error:", e);
			e.printStackTrace();
		}
	}

	// =========================================================
	public Object getBean(String name) throws BeansException {
		return getBeanFactory().getBean(name);
	}

	public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
		return getBeanFactory().getBean(name, requiredType);
	}

	public <T> T getBean(Class<T> requiredType) throws BeansException {
		return getBeanFactory().getBean(requiredType);
	}

	public <T> T getBean(BeanKey beankey) throws BeansException {
		return getBeanFactory().getBean(beankey);
	}

	// =============================================================
	// 注册Configuration
	public void register(Class<?>... configClasses) {
		Assert.notEmpty(configClasses);
		for (Class<?> clazz : configClasses) {
			registerBean(null, clazz);
			if (clazz.isAnnotationPresent(Configuration.class)) {
				postProcessorConfigurationClass(clazz);
			}
		}
	}

	// 扫描包
	public void scan(String... basePackages) {
		Assert.notEmpty(basePackages);
		getEnvironment().addFrameProp(FRAME.COMPONENT_SCAN, JUtil.join(basePackages, ";"));
	}

	// 注册普通bean
	public BeanDefinition registerBean(String name, Class<?> clazz) {
		return getBeanDefinitions().buildBeanDefinition(name, clazz);
	}

	// 注册普通bean
	public BeanDefinition registerBean(Class<?> clazz) {
		return getBeanDefinitions().buildBeanDefinition(null, clazz);
	}

	/**
	 * 处理@Configuration
	 */
	protected void postProcessorConfigurationClass(Class<?> clazz) {
		if (clazz.isAnnotationPresent(ComponentScan.class)) {
			String[] scanPath = clazz.getAnnotation(ComponentScan.class).value();
			getEnvironment().addFrameProp(FRAME.COMPONENT_SCAN, JUtil.join(scanPath, ";"));
		}
		if (clazz.isAnnotationPresent(PropertySource.class)) {
			String[] propPath = clazz.getAnnotation(PropertySource.class).value();
			getEnvironment().addFrameProp(FRAME.FILE_PROP, JUtil.join(propPath, ";"));
		}
		// 获取所有import
		List<Import> importannos = ClassUtil.findAnnotation(clazz, Import.class);
		if (importannos != null) {
			for (Import importanno : importannos) {
				Class<?>[] propPath = importanno.value();
				register(propPath);
				for (Class<?> classImport : propPath)
					if (ImportSelector.class.isAssignableFrom(classImport))
						importClassList.add(classImport);
			}
		}
	}

	/**
	 * 处理@Import
	 */
	protected void postProcessImportClass()
			throws InstantiationException, IllegalAccessException, BeansException, ClassNotFoundException {
		for (Class<?> clazz : importClassList) {
			String[] config = ((ImportSelector) clazz.newInstance()).selectImports(this);
			for (String str : config)
				registerBean(null, Class.forName(str));
		}
	}

	/**
	 * 初始化Bean
	 */
	protected void postProcessBuildBeans() {
		beanFactory.buildBean(new BeanKey(ApplicationContext.class), this, EScope.SINGLETON);
		beanFactory.buildBean(new BeanKey(BeanDefinitions.class), getBeanDefinitions(), EScope.SINGLETON);
		beanFactory.buildBean(new BeanKey(Environment.class), getEnvironment(), EScope.SINGLETON);
		beanFactory.buildBean(new BeanKey(BeanFactory.class), getBeanFactory(), EScope.SINGLETON);
		beanFactory.buildBean(new BeanKey(FactoryBean.class), getFactoryBean(), EScope.SINGLETON);
		beanFactory.buildBean();
	}

	/**
	 * 执行事件通知
	 */
	@Override
	public void publishEvent(ApplicationEvent event) {
		try {
			Object obj = getBeanFactory().getEventBean(event);
			Method m = ApplicationListener.class.getMethod("onApplicationEvent", ApplicationEvent.class);
			m.invoke(obj, event);
		} catch (Exception e) {
			log.error("PublishEvent ERROR:", e);
		}
	}

	public boolean isRuning() {
		return state;
	}

	/**
	 * Banner txt:http://www.network-science.de/ascii/
	 * pic:http://www.degraeve.com/img2txt.php
	 */
	public void postProcessBanner() {
		try {
			InputStream file = ApplicationContext.class.getClassLoader().getResourceAsStream("banner.txt");
			if (file != null) {
				byte[] txt = StreamUtil.toByteArray(file);
				log.info("Start:\n" + new String(txt));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
